/*
 * In this module we will see how to manage telemetry data in the Item Service. Telemetry data
 * is data collected from remote data points such as sensors or BMS equipment and comes as time
 * series data (meaning the usual a series of readings with timestamps for when the readings was taken).
 * The Item Service provides a way to manage your sensors in a NamedTelemetryCollection which is backed
 * by a special time series type of collection created for managing timestamped readings data.
 *
 * BE SURE YOU HAVE FIRST COMPLETED THE SELF-LED DEVELOPER BASICS COURSE BEFORE PROCEEDING
*/

let telemetryIntermediateModule = {

	getRunnableScripts() {
		return [
			{ name: "Step 2 - Create Test NamedTelemetryCollections", script: "createCollections" },
			{ name: "Step 3 - Create Sensors as Telemetry Items", script: "createTelemetryItems" },
			{ name: "Step 4 - Create Sensor Readings", script: "createReadingItems" },
			{ name: "Step 4 - Get Sensor Readings", script: "getSensorReadings" }
		]
	},
  
	/*
	 * STEP 1 - CREATE A NEW SCRIPT
	 *
	 * Sign in to the TWINIT.DEV extension and the training environment using the training application.
	 * Expand your project and expand the Scripts node in the tree.
	 * Right click on the Scripts node and click 'Create New Script'
	 * For Script Name enter 'INT05 - Telemetry Data' 
	 * For Script Description enter 'Telemetry Data and Item Service'
	 * For Script Short Name enter 'telem'
	 * For Script User Type enter 'telem'
	 * Double the new script to open it
	 * Copy and paste the contents of this file into the script and save it
	 * Then right click on the script and select 'Commit to New Version'
	 * 
	*/

	/*
	 * STEP 2 - CREATE NAMEDTELEMETRYCOLLECTIONS
	 *
	 * The first step to managing telemetry data is to create NamedTelemetryCollections to contain
	 * the items which represent your sensors or IOT devices. You can create NamedTelemetryCollections
	 * as best suit you, but one good way is to create one NamedTelemetryCollection per type of sensor.
	 * 
	 * When you create a NamedTelemetryCollection you do so much as you create a NamedUserCollection.
	 * However, with NamedTelemetryCollections you will also provide soem time series data such as the
	 * granularity of the readings that you will be managing for the sensors in the collection.
	 * You will need to select either 'seconds', 'minutes', or 'hours.
	 * 
	 * When deciding how you will create NamedTelemetryCollections it si good to consider the reading
	 * granularity, and to keep sensors that have different reading granularities in different collections.
	 * 
	 * Review the script below.
	 * In this example we will create a NamedTelemetryCollection for our CO2 sensors and one for
	 * temperature sensors. Both will have sensor readings every 5 minutes, so we will set their
	 * granularities to 'minutes.
	 * 
	 * Right click on this script and run the 'Step 2 - Create Test NamedTelemetryCollections' script.
	 * 
	*/
	async createCollections(input, libraries, ctx) {

		const { IafItemSvc } = libraries.PlatformApi

		let newCO2Collection = await IafItemSvc.createNamedUserItems([{
			_name: 'CO2 Sensor Telemetry',
			_description: 'CO2 Sensor Telemetry',
			_shortName: 'co2sensors',
			_userType: 'co2sensors',
			_namespaces: ctx._namespaces,
         _tsProperties: {
            _granularity: 'minutes', // if not provided minutes is default
         }
		}], 'NamedTelemetryCollection', ctx) // must privde the NamedTelemetryCollection itemClass

      let newTempCollection = await IafItemSvc.createNamedUserItems([{
			_name: 'Temperature Sensor Telemetry',
			_description: 'Temperature Sensor Telemetry',
			_shortName: 'tempsensors',
			_userType: 'tempsensors',
			_namespaces: ctx._namespaces,
         _tsProperties: {
            _granularity: 'minutes',
         }
		}], 'NamedTelemetryCollection', ctx)

		return { newCO2Collection, newTempCollection }

	},

	/*
	 * STEP 3 - ADD CO2 AND TEMPERATURE SENSORS
	 *
	 * In this step we will add our two types of sensors to their respective NamedTelemetryCollections as
	 * Telemetry Items. Creating Telemetry Items is very similar to creating other items in the item service.
	 * 
	 * Telemetry Items have an additional requirement however. Each Telemetry Item must contain a _sourceId.
	 * This _sourceId is the id of the sensor or IOT device in the external system. This _sourceId is
	 * important as it will be used when we create readings for the sensors.
	 * 
	 * Review the 'createTelemetryItems' script below.
	 * We'll be creating sensors based off data in a provided Sensors.xlsx.
	 * Each sensor will have these properties:
	 *  - id: the id of the sensor or device in the external system
	 *  - _sourceId: the same as id
	 *  - sensor_type: the type of sensor, either c02 or temperature
	 *  - units: the units for the readings from the sensor
	 *  - location: the location of the sensor (what room its in for instance)
	 * 
	 * Right click on this script and run the 'Step 3 - Create Sensors as Telemetry Items' script.
	 * When prompted select supporting_files/Sensors.xlsx
	 * 
	 * Inspect the results.
	 * 
	*/
	async createTelemetryItems(input, libraries, ctx) {

		const { PlatformApi, UiUtils } = libraries
		const { IafItemSvc } = PlatformApi

		// Next we will create the sensors as Telemetry Items. THis will allows us to them add readings
		// for the sensors to the telemetry data.
		// We will select the sensor data file from disk
		// Select supporting_files/Sensors.xlsx when prompted
		// For real-world implementation this sensor info will probably be coming from another system, here
		// we are simply imitating that with a spreadsheet
		const dataFile = await UiUtils.IafLocalFile.selectFiles({ multiple: false, accept: ".xlsx" })
		let workbooks = await UiUtils.IafDataPlugin.readXLSXFiles(dataFile)
		let wbJSON = await UiUtils.IafDataPlugin.workbookToJSON(workbooks[0])
		let sensorSheet = wbJSON.sensors
		let sensorInfo = await UiUtils.IafDataPlugin.parseGridData({ gridData: sensorSheet })

		// the NamedTelemetryCollection requires that every Telemetry item have a _sourceId
		// so we will duplicate the id on each sensor as _sourceId
		sensorInfo.forEach(s => s._sourceId = s.id)

		// next we will sort out the two types of sensors to be written to their own NamedTelemetryCollection
		let co2Sensors = sensorInfo.filter(s => s.sensor_type === 'co2')
		let tempSensors = sensorInfo.filter(s => s.sensor_type === 'temperature')

		// get the NamedTelemetryCollections
		let co2Collection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'co2sensors'}}, ctx))._list[0]
		let tempCollection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'tempsensors'}}, ctx))._list[0]

		// use createRelatedItems to create the Telemetry Items
		// also use {transactional: true} in the options to specify that creations of the items should happen as a transaction
		let createCO2Result = await IafItemSvc.createRelatedItems(co2Collection._userItemId, co2Sensors, ctx, {transactional: true})
		let createTempResult = await IafItemSvc.createRelatedItems(tempCollection._userItemId, tempSensors, ctx, {transactional: true})

		return { createCO2Result, createTempResult }
	},

	/*
	 * STEP 3 - ADD SENSOR READINGS 
	 *
	 * In this step we add readings for each sensor. We will read sensor data in from a spreadsheet.
	 * 
	 * When adding sensor readings, you always add them to the Telemetry Items in a NamedTelemetryCollection.
	 * As such, reading items have some additional requirements.
	 * Reading objects must have:
	 *  - _ts: a timestamp value for the reading
	 *  - _tsMetadata._telItemId: the _id of the Telemetry Item the reading is for (the sensor _id)
	 *  - _tsMetadata._sourceId: the _sourceId of the Telemetry Item the reading is for (the sensor _sourceId)
	 * All the rest of the data on a reading is up to you based on the type of reading you are creating.
	 * The readings we create will also have a 'reading' with value from the sensor and a 'readingType' with
	 * the type of reading the value is (co2 or temperature).
	 * 
	 * Review the 'createReadingItems' script below. 
	 * 
	 * Right click on this script and run the 'Step 4 - Create Sensor Readings' script.
	 * When prompted select supporting_files/Readings.xlsx
	 * 
	 * Inspect the results.
	 * 
	*/
	async createReadingItems(input, libraries, ctx) {

		const { PlatformApi, UiUtils } = libraries
		const { IafItemSvc } = PlatformApi

		// Next we will now add readings from the sensor Telemetry Items we created.
		// We will select the readings data file from disk
		// Select supporting_files/Readings.xlsx when prompted
		// For real-world implementation this readoing info will probably be coming from another system, here
		// we are simply imitating that with a spreadsheet
		const dataFile = await UiUtils.IafLocalFile.selectFiles({ multiple: false, accept: ".xlsx" })
		let workbooks = await UiUtils.IafDataPlugin.readXLSXFiles(dataFile)
		let wbJSON = await UiUtils.IafDataPlugin.workbookToJSON(workbooks[0])
		let readingsInfo = await UiUtils.IafDataPlugin.parseGridData({ gridData: wbJSON.readings })

		// for every reading change the reading value from a string to numbers
		// xlsx reads everything in as a string
		readingsInfo.forEach(r => r.reading = Number(r.reading))

		// separate the CO2 and the Temperature readings
		let co2Readings = readingsInfo.filter(r => r.readingType === 'co2')
		let tempReadings = readingsInfo.filter(r => r.readingType === 'temperature')

		// get the unique _sourceIds of the readings
		// we need the _sourceIds so we can fetch the Telemetry Items for which we are creating readings
		let co2SensorSourceIds = co2Readings.map(s => s._sourceid)
		co2SensorSourceIds = co2SensorSourceIds.filter((value, index, array) => array.indexOf(value) === index);
		let tempSensorSourceIds = tempReadings.map(s => s._sourceid)
		tempSensorSourceIds = tempSensorSourceIds.filter((value, index, array) => array.indexOf(value) === index);

		// get the NamedTelemetryCollections
		let co2Collection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'co2sensors'}}, ctx))._list[0]
		let tempCollection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'tempsensors'}}, ctx))._list[0]

		// get the CO2 and Temperature Telemetry Items
		let co2TelemetryItems = (await IafItemSvc.getRelatedItems(co2Collection._userItemId, {
			query: {_sourceId : {$in: co2SensorSourceIds}}
		}, ctx))._list

		let tempTelemetryItems = (await IafItemSvc.getRelatedItems(tempCollection._userItemId, {
			query: {_sourceId : {$in: tempSensorSourceIds}}
		}, ctx))._list

		// construct the readings objects
		// Telemetry REadings data must have:
		// _ts: timestamp - our readings we imported already have this property
		// _tsMetadata._tellItemId: the _id of the Telemetry Item the reading is for
		// _tsMetadata._sourceId: the _sourceId on the Telemtry Item
		co2Readings.forEach((r) => {
			let telItem = co2TelemetryItems.find(ti => ti._sourceId === r._sourceid)
			r._tsMetadata = {
				_telItemId: telItem._id,
				_sourceId: telItem._sourceId
			}
		})

		tempReadings.forEach((r) => {
			let telItem = tempTelemetryItems.find(ti => ti._sourceId === r._sourceid)
			r._tsMetadata = {
				_telItemId: telItem._id,
				_sourceId: telItem._sourceId
			}
		})

		// create another array we will modify as we create reading items
		let co2ReadingsCopy = [...co2Readings]
		let tempReadingsCopy = [...tempReadings]

		// now add the readings to the telemetry data
		// we add 100 readings at a time
		let co2CreateResults = []
		while (co2ReadingsCopy.length) {
			let createThese = co2ReadingsCopy.splice(0, 100)
			let co2CreateRes = await IafItemSvc.createRelatedReadingItems(co2Collection._userItemId, createThese, ctx)
			co2CreateResults.push(co2CreateRes)
		}
		let tempCreateResults = []
		while (tempReadingsCopy.length) {
			let createThese = tempReadingsCopy.splice(0, 100)
			let tempCreateRes = await IafItemSvc.createRelatedReadingItems(tempCollection._userItemId, createThese, ctx)
			tempCreateResults.push(tempCreateRes)
		}
		
		return { readingsToCreate: {tempReadings, co2Readings }, createResults: {co2CreateResults,tempCreateResults}}
	},

	/*
	 * STEP 5 - QUERYING SENSORS AND SENSOR READINGS 
	 *
	 * In this step we will query or sensors and readings from those sensors.
	 * 
	 * Review the 'getSensorReadings' script below.
	 * Notice that we always fetch readings through a NamedTelemetryCollection but provide a query for the readings.
	 * 
	 * Right click on this script and run the 'Step 4 - Get Sensor Readings' script.
	 * 
	 * Inspect the results.
	 * 
	*/
	async getSensorReadings(input, libraries, ctx) {

		const { IafItemSvc } = libraries.PlatformApi

		// get the NamedTelemetryCollections
		let co2Collection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'co2sensors'}}, ctx))._list[0]
		let tempCollection = (await IafItemSvc.getNamedUserItems({query: {_userType: 'tempsensors'}}, ctx))._list[0]

		// get the Main Conference Room Sensors for each type
		let mainConferenceRoomCO2Sensor = (await IafItemSvc.getRelatedItems(co2Collection._userItemId, {query: {location: 'Main Conference Room'}}, ctx))._list[0]
		let mainConferenceRoomTempSensor = (await IafItemSvc.getRelatedItems(tempCollection._userItemId, {query: {location: 'Main Conference Room'}}, ctx))._list[0]
	
		// lets get the ten latest readings for CO2 in the main conference room
		let lastTenCO2Readings = await IafItemSvc.getRelatedReadingItems(co2Collection._userItemId, // query the telemetry items in teh NamedTelemetryCollection
			{query: {'_tsMetadata._telItemId': mainConferenceRoomCO2Sensor._id}}, // find readings by _tsMetadata only for main conference room sensor
			ctx,
			{
				sort: {'_ts': -1},		// sort by timestamp descending
				page: {_pageSize: 10}	// return the top ten results
			}
		)

		// now lets get any time the temperature was over 28 Celsius
		// for the main conference room
		// from 2pm and after
		let highTempReadings = await IafItemSvc.getRelatedReadingItems(tempCollection._userItemId, // query the telemetry items in the NamedTelemetryCollection
			{query: {$and: [
				{'_tsMetadata._telItemId': mainConferenceRoomTempSensor._id}, // find temperature readings for main conference room
				{reading: {$gte: 28}}, // reading for temperature greater than or equal to 28
				{_ts: {$gte: "2023-01-02T14:00:00.000Z"}}
			]}},
			ctx
		)

		return { lastTenCO2Readings, highTempReadings }
	},  
}
  
export default telemetryIntermediateModule
